# @author Franciz
# @date 2023/3/16
import asyncio
import concurrent
import csv
import multiprocessing
import time
from concurrent.futures import ThreadPoolExecutor

import psutil
from m_mock.m_random import m_date

from monit.common import get_csv_name, create_next_date_csv, create_csv


class ProcessMonitorInfo:
    header = ["date", "time", "cpu %/s", "used mem%", "io_input MB/s", "io_output MB/s"]

    cpu_percent = 0  # CPU百分比
    used_memory_percent = 0  # 已用内存百分比/MB
    used_memory = 0  # 已用内存/MB
    io_input = 0  # 接收MB/s 或者 磁盘写入
    io_output = 0  # 发送MB/s 或者 磁盘读取

    status = 0
    process_size = 0

    def __init__(self, name, port=None, interval_time=5, wait_init=False):
        self.name = name
        self.port = port
        self.interval_time = interval_time
        self.wait_time = self.interval_time if self.interval_time <= 2 else self.interval_time - 1

        self.wait_init = wait_init

        self.proc_names = []  # 进程名称的列表
        self.process_list = self.get_pids()
        self.cpu_percent_list = []  # 进程名称的CPU使用率的列表

    def get_monit_info(self):
        # [self.cpu_percent, self.used_memory, self.used_memory_percent, self.process_size, self.status]
        return [self.cpu_percent, self.used_memory_percent, self.io_input, self.io_output]

    def get_pids(self):
        """

        :return: 根据程序的名称如:Thunder.exe 返回对应程序活动的进程列表
        """

        def add(proc1, procs1):
            procs1.append(proc1)
            children_pids = proc1.children()
            if len(children_pids) > 0:
                for i in proc1.children():
                    procs1.append(i)

        name = self.name
        procs = []
        for proc in psutil.process_iter(attrs=['pid', 'name', 'status', 'environ']):
            try:
                if name in proc.name() and proc.status() in (psutil.STATUS_RUNNING, psutil.STATUS_SLEEPING):
                    # 获取名称对应且是运行中的改程序的所有进程(含子进程)
                    # pid = proc.pid
                    # cmdline = proc.as_dict().get('cmdline')
                    # print(json.dumps(proc.as_dict(), indent=2))
                    proc_environ = proc.environ()
                    proc_port = None if proc_environ is None else proc_environ.get('PORT')
                    if self.port is not None and proc_port and self.port == proc_port:
                        # 根据名称和端口号确定监控的进程
                        add(proc, procs)
                    elif self.port is None:
                        # 根据名称确定监控的进程
                        add(proc, procs)
            except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
                pass
        # self.proc_names = json.dumps([i.name() for i in procs], ensure_ascii=False)
        return procs

    async def get_used_memory(self):
        """

        :return: 已用内存
        """
        memory_list = []
        for proc in self.process_list:
            memory = proc.memory_full_info().uss
            memory_list.append(round(memory / 1024 / 1024, 2))
        self.used_memory = float('%.2f' % sum(memory_list))
        return self.used_memory

    async def get_used_memory_percent(self):
        """

        :return: 已用内存百分比
        """
        memory_percent_list = []
        for proc in self.process_list:
            memory_percent = proc.memory_percent()
            memory_percent_list.append(memory_percent)
        self.used_memory_percent = float('%.2f' % sum(memory_percent_list))
        return self.used_memory_percent

    def cpu_wait(self):
        if self.interval_time > 1 and self.wait_init:
            time.sleep(self.interval_time - 1)

    cpu_percent_list = []

    async def get_process_cpu_percent(self):
        """
        :return: 对应进程的cpu的使用频率
        """

        async def cpu_percent(pid):
            try:
                # Get process
                p = psutil.Process(pid)

                # Calculate CPU usage asynchronously
                cpu_usage = await asyncio.to_thread(p.cpu_percent, 1)
                return min(cpu_usage, 100.0)
            except (psutil.AccessDenied, psutil.NoSuchProcess):
                return 0.0

        with concurrent.futures.ThreadPoolExecutor(max_workers=multiprocessing.cpu_count() * 2) as executor:
            futures = [cpu_percent(proc.pid) for proc in self.process_list]
            self.cpu_percent_list = await asyncio.gather(*futures)

        # 过滤掉为0的值再计算平均值
        non_zero_values = [value for value in self.cpu_percent_list if value > 0]
        if non_zero_values:
            cpu_percent_avg = sum(non_zero_values) / len(non_zero_values)
        else:
            cpu_percent_avg = 0.0
        self.cpu_percent = min(float('%.2f' % cpu_percent_avg), 100.0)

    async def get_io_counters(self):
        # 初始化总的网络输入和输出字节数
        total_bytes_recv_s = 0
        total_bytes_sent_s = 0
        total_bytes_recv_e = 0
        total_bytes_sent_e = 0

        start_time = time.time()
        # 遍历所有子进程
        for proc in self.process_list:
            try:
                # 获取该子进程的网络IO信息
                io_counters = proc.io_counters()
                # 累加网络输入和输出字节数
                total_bytes_recv_s += io_counters.write_bytes
                total_bytes_sent_s += io_counters.read_bytes
            except (psutil.AccessDenied, psutil.NoSuchProcess):
                # 处理访问被拒绝的情况
                pass
        # 等待1秒钟
        await asyncio.sleep(1)
        for proc in self.process_list:
            try:
                # 获取该子进程的网络IO信息
                io_counters = proc.io_counters()
                # 累加网络输入和输出字节数
                total_bytes_recv_e += io_counters.write_bytes
                total_bytes_sent_e += io_counters.read_bytes
            except (psutil.AccessDenied, psutil.NoSuchProcess):
                # 处理访问被拒绝的情况
                pass
        end_time = time.time()

        # 计算网络输入和输出字节数的差值
        bytes_recv = total_bytes_recv_e - total_bytes_recv_s
        bytes_sent = total_bytes_sent_e - total_bytes_sent_s

        # 计算每秒钟的网络输入和输出速率
        self.io_input = round(bytes_recv / (end_time - start_time) / 1024 / 1024, 2)
        self.io_output = round(bytes_sent / (end_time - start_time) / 1024 / 1024, 2)
        # print("发送网速MB/s:", speed_sent)
        # print("接收网速MB/s:", speed_recv)
        # print(f'耗时:{end_time - start_time}')

        # 返回网络IO的总和
        return self.io_input, self.io_output

    async def get_monitor_info(self):
        proc_size = len(self.process_list)
        self.process_size = proc_size
        if proc_size > 0:
            self.status = 1
            await asyncio.gather(self.get_used_memory(), self.get_used_memory_percent(), self.get_io_counters(),
                                 self.get_process_cpu_percent())
        else:
            self.status = 0
        return self.get_monit_info()


def process_monitor_info_record_to_file(process_name, process_port=None, file_period=1, wait_time=5):
    """

    :param process_port: 进程的端口
    :param process_name: 软件名称,示例:java.exe
    :param wait_time: 间隔时间/秒
    :param file_period:  记录文件创建周期/天
    :return:
    """
    raw_file_time = m_date.date()
    pr_name = process_name
    start_info = f'# 监控的软件名称:{pr_name},端口:{process_port},记录文件创建周期:{file_period}天/次,间隔时间:{wait_time}秒/次'
    print(start_info)
    # header = ['date', 'time', 'cpu %/s', 'used mem/MB', 'used mem%', 'processes/num', 'status(1 alive/0 dead)',
    #           'process list']
    header = ProcessMonitorInfo.header
    file_name = get_csv_name(raw_file_time, pr_name)
    create_csv(header, file_name)
    wait_init = False
    while True:
        # start_time = time.perf_counter()
        if file_period is not None:
            file_name, raw_file_time = create_next_date_csv(file_name, file_period, header, pr_name, raw_file_time)
        with open(file_name, 'a+', encoding='utf-8', newline='') as file_obj:
            # 1:创建writer对象
            writer = csv.writer(file_obj)
            process_monitor_info = ProcessMonitorInfo(name=pr_name, port=process_port, interval_time=wait_time,
                                                      wait_init=wait_init)
            try:
                result = asyncio.run(process_monitor_info.get_monitor_info())
            except Exception as e:
                print(str(e))
                result = process_monitor_info.get_monit_info()
            finally:
                wait_init = True
                if process_monitor_info.status == 0:
                    process_monitor_info.cpu_wait()
            line = (m_date.date(), m_date.time(), *result)
            print(*line)
            writer.writerow(line)
        # end_time = time.perf_counter()
        # time_diff = end_time - start_time
        # print(f'耗时:{time_diff}')


def handle_detail(detail, line):
    if detail is False:
        # 不展示细节则删除进程列表
        line = line[:-1]
    return line
